#include "plan.h"
#include "ui_plan.h"
#include "addfunc.h"
#include <QDebug>
#include <QPushButton>
#include <QWheelEvent>//鼠标滑轮事件
#include <QColorDialog>//为用户提供颜色选择的对话框控件

Plan::Plan(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Plan),oldX(0), oldY(0)//声明两个私有变量
{
    ui->setupUi(this);
    setWindowIcon(QIcon(":/image/P_mathematics.svg.png"));
    setWindowTitle("绘制平面图形");
    canmovemousexy=false;
    //事件过滤器，实现监视作用
    //坐标范围
    ui->Xmin->installEventFilter(this);
    ui->Xmax->installEventFilter(this);
    ui->showYmin->installEventFilter(this);
    ui->showYmax->installEventFilter(this);
    ui->showXmin->installEventFilter(this);
    ui->showXmax->installEventFilter(this);
    ui->frame->installEventFilter(this);
    //颜色
    ui->color->installEventFilter(this);
    //函数列表
    ui->list->setModel(&myList);//将myList设置为list的列表控件的数据模型

    //QThread是Qt框架中提供的一个线程类，用于支持多线程编程
    thread = new QThread(this);
    avoid = new Avoid();
    avoid->moveToThread(thread);//avoid移动到线程thread中执行
    //这样，avoid 对象就可以在 thread 线程中执行任务，而不是在主线程中执行
    //在移动对象时，Qt会自动为该对象创建一个事件循环，以便在该线程中处理事件和信号
    thread->start();//启动线程
    connect(this, &Plan::calc, avoid, &Avoid::calc);
    connect(avoid, &Avoid::over, this, [this]()
    {
        ui->frame->update();
    });

    //右键删除函数
    ui->list->setContextMenuPolicy(Qt::CustomContextMenu);//添加自定义菜单
    menu = new QMenu(this);
    auto *action = new QAction(this);//自动推导出变量类型
    action->setText(QString("删除该函数"));
    menu->addAction(action);

    connect(ui->addbtn, &QPushButton::clicked, this, &Plan::gotoAddfunc);
    connect(ui->resetbtn, &QPushButton::clicked, this, [this]()
    {
        //复原操作
        //坐标轴回归
        ui->frame->m_Ymin=-10;
        ui->frame->m_Ymax=10;
        ui->frame->m_Xmin=-10;
        ui->frame->m_Xmax=10;
        //xy轴显示范围回归
        ui->showXmin->setValue(-10);
        ui->showXmax->setValue(10);
        ui->showYmin->setValue(-10);
        ui->showYmax->setValue(10);
        ui->frame->update();
    });

    connect(ui->list, &QListView::customContextMenuRequested, this, &Plan::FuncMenu);//右键显示菜单
    connect(action, &QAction::triggered, this, &Plan::delFunc);//删除该函数
    connect(ui->OKbtn,&QPushButton::clicked,this,&Plan::updateFunc);//修改参数后更新函数
    connect(ui->list,&QListView::clicked,this,&Plan::updateFuncSetting);//将列表中所选的函数保存的设置显示出来

    connect(ui->gridbtn,&QCheckBox::stateChanged,this,&Plan::updateXyGrid);//是否绘制网格
    connect(ui->xybtn,&QCheckBox::stateChanged,this,&Plan::updateXyGrid);//是否绘制坐标轴
    connect(ui->movemouse,&QCheckBox::stateChanged,[=](int x){//是否使用移动鼠标显示坐标功能
        this->canmovemousexy=(x==2);
    });

    ui->list->setCurrentIndex(myList.index(0));
}

Plan::~Plan()
{
    thread->quit();//向线程发送退出请求
    thread->wait();//等待线程退出
    delete ui;
}

void Plan::gotoAddfunc()
{
    auto *addfunc = new Addfunc(this);
    connect(addfunc, &Addfunc::hitOK, this, [this](MyFunc *function)//监听
    {
        ui->Xmax->setValue(function->mymax);
        ui->Xmin->setValue(function->mymin);
        ui->color->setText(QString::fromStdString(function->mycolor));
        ui->precision->setValue(function->mystep);
        ui->Style->setCurrentIndex(function->mystyle - 1);
        ui->isShow->setCheckState(Qt::Checked);
        myList.addFunc(function);
        if(myList.funcList.size()==1)//大小为1的时候更新设置
        {
            //因为funcList中仅有一个元素时，列表中的第一个元素和第二个元素实际上是同一个元素
            //myList.index(0)和myList.index(1)都会返回这个元素的索引
            //因此，第一行代码将界面控件的选中项设置为这个元素的索引，而第二行代码将这个索引作为参数传递给updateFuncSetting函数
            ui->list->setCurrentIndex(myList.index(0));
            updateFuncSetting(myList.index(1));
        }

        emit calc(function);
    });
    addfunc->open();
}

bool Plan::eventFilter(QObject *obj, QEvent *e)
{
    switch (e->type())
    {
    case QEvent::StyleAnimationUpdate://限制范围选择
    {
        if (obj == ui->showXmax || obj == ui->showXmin || obj == ui->showYmin || obj == ui->showYmax)//限制绘图范围选择
        {
            //上下左右至少相距1
            if (ui->showXmax->value() - ui->showXmin->value() <= 1)
                ui->showXmin->setValue(ui->showXmax->value() - 1);
            if (ui->showYmax->value() - ui->showYmin->value() <= 1)
                ui->showYmin->setValue(ui->showYmax->value() - 1);
            updateXYrange();
        }
        else if (obj == ui->Xmax || obj == ui->Xmin)//限制计算范围选择
        {
            if( ui->Xmax->value()<ui->Xmin->value())//max不能小于min
            {
                ui->Xmin->setValue(ui->Xmax->value());
            }
        }
        return true;
    }
    case QEvent::Wheel://滚轮
    {
        if (obj == ui->frame)
        {
            auto *ee = static_cast<QWheelEvent *>(e);//强制转化类型
            stretchXYrange(ee->angleDelta().y());//y()表示鼠标滚轮事件在垂直方向上的滚动量
            return true;
        }
        break;
    }
    case QEvent::MouseButtonPress:
    {
        if (obj == ui->frame)//会判断是否在ui->frame对象上按下鼠标
        {
            auto *me = static_cast<QMouseEvent *>(e);
            //记录按下鼠标时的坐标
            oldX = ui->frame->DxtoLx(me->x());
            oldY = ui->frame->DytoLy(me->y());
        }
        else if (obj == ui->color)//颜色选择框
        {
            auto c = QColorDialog::getColor();
            if (c.isValid())
                ui->color->setText(c.name());
        }
        return true;
    }
    case QEvent::MouseButtonRelease:
        if (obj == ui->frame)//会判断是否在ui->frame对象上释放鼠标
        {
            auto *me = static_cast<QMouseEvent *>(e);
            moveXYrange(me->pos());//鼠标移动距离
            return true;
        }
        break;
    case QEvent::MouseButtonDblClick:
        if (obj == ui->frame)
        {
            auto *me = static_cast<QMouseEvent *>(e);
            ui->Xcoo->setText(QString::asprintf("%.2f", ui->frame->DxtoLx(me->x())));//两位小数
            ui->Ycoo->setText(QString::asprintf("%.2f", ui->frame->DytoLy(me->y())));
            return true;
        }
        break;
    case QEvent::MouseMove:
        if (obj == ui->frame)
        {
            if(canmovemousexy)
            {
                auto *me = static_cast<QMouseEvent *>(e);
                ui->Xcoo->setText(QString::asprintf("%.2f", ui->frame->DxtoLx(me->x())));
                ui->Ycoo->setText(QString::asprintf("%.2f", ui->frame->DytoLy(me->y())));
                return true;
            }
        }
        break;
    }
    return QWidget::eventFilter(obj, e);
}

void Plan::updateXYrange()
{
    ui->frame->m_Xmax = ui->showXmax->value();
    ui->frame->m_Xmin = ui->showXmin->value();
    ui->frame->m_Ymax = ui->showYmax->value();
    ui->frame->m_Ymin = ui->showYmin->value();
    ui->frame->update();
}

//鼠标拖动逻辑
void Plan::moveXYrange(QPoint releasePos)
{

    double nowX = ui->frame->DxtoLx(releasePos.x());//释放时鼠标的坐标
    double nowY = ui->frame->DytoLy(releasePos.y());
    double dx = nowX - oldX;
    double dy = nowY - oldY;

    double maxX = ui->showXmax->value() - dx;
    double minX = ui->showXmin->value() - dx;
    double maxY = ui->showYmax->value() - dy;
    double minY = ui->showYmin->value() - dy;

    //没有越范围 才移动
    if (maxX <= 99 && minX >= -99 &&
        maxY <= 99 && minY >= -99)
    {
        ui->showXmin->setValue(minX);
        ui->showXmax->setValue(maxX);
        ui->showYmin->setValue(minY);
        ui->showYmax->setValue(maxY);
        updateXYrange();
    }
}

//鼠标滚轮逻辑
void Plan::stretchXYrange(int d)
{

    double xMax = ui->showXmax->value();
    double xMin = ui->showXmin->value();
    double yMax = ui->showYmax->value();
    double yMin = ui->showYmin->value();
    d = d / 40;
    //当放大到差值小于等于1的时候停止放大
    if (d < 0 || ((xMax - xMin > 1 || yMax - yMin > 1) && d != 0))
    {
        xMax -= d * 0.1;
        xMin += d * 0.1;
        yMax -= d * 0.1;
        yMin += d * 0.1;
        if (xMax <= 100 && xMin >= -99 &&
            yMax <= 100 && yMin > -99&&
            xMax>xMin&&yMax>yMin)
        {
            ui->showXmin->setValue(xMin);
            ui->showXmax->setValue(xMax);
            ui->showYmin->setValue(yMin);
            ui->showYmax->setValue(yMax);
            updateXYrange();
        }

    }
}

//删除选定的函数并更新图形
void Plan::FuncMenu(QPoint point)
{
    QModelIndex index = ui->list->indexAt(point);//获取在指定的QPoint位置处的列表控件ui->list中的项的模型索引index
    if (index.isValid())//说明该处有一个项
        menu->exec(QCursor::pos());//函数获取当前鼠标位置，并将其作为参数传递给exec()函数，以便在该位置显示菜单
    //exec用于在当前鼠标位置显示菜单，并等待用户的选择操作。
}

void Plan::delFunc()
{
    QModelIndex index = ui->list->currentIndex();//返回当前选中项的位置信息
    if (index.isValid())
    {
        myList.rmFunc(index.row());//row()返回该单元格的行数
        ui->frame->update();
    }
}

void Plan::updateFunc()//更新当前选定函数的参数并更新图形
{
    int row=ui->list->currentIndex().row();
    if(row<myList.funcList.size())//将修改存入
    {
        MyFunc* function=myList.funcList[row];
        function->mystyle= ui->Style->currentIndex()+1;
        function->mycolor=ui->color->text().toStdString();
        function->canDraw=(ui->isShow->checkState()==Qt::Checked);
        if(function->mymax!=ui->Xmax->value()||function->mymin!=ui->Xmin->value()||function->mystep!=ui->precision->value())
        {
            function->mymin=ui->Xmin->value();
            function->mymax=ui->Xmax->value();
            function->mystep=ui->precision->value();
            emit calc(function);
        }
        ui->frame->update();
    }
}

void Plan::updateFuncSetting(const QModelIndex &index)//更新列表中当前所选函数的设置，并相应地更新UI
{
    if (index.isValid())//这个地方有个函数
    {
        MyFunc *function=myList.funcList[index.row()];
        ui->Xmax->setValue(function->mymax);
        ui->Xmin->setValue(function->mymin);
        ui->color->setText(QString::fromStdString(function->mycolor));
        ui->precision->setValue(function->mystep);
        ui->Style->setCurrentIndex(function->mystyle - 1);
        if(function->canDraw)
            ui->isShow->setCheckState(Qt::Checked);
        else
            ui->isShow->setCheckState(Qt::Unchecked);
    }
}

void Plan::updateXyGrid(int x)//更新是否根据用户的选择在图形中绘制网格和坐标线
{
    QCheckBox* cb=static_cast<QCheckBox*>(sender());//发送信号的box
    if(cb->text()=="轴")
        //0 按钮未被选中
        //1 按钮部分选中
        //2 按钮全部被选中
        ui->frame->canDrawXy=(x==2);
    else
        ui->frame->canDrawGrid=(x==2);
    ui->frame->update();
}
